#!/usr/bin/perl
use strict;
use warnings;

use Archive::Zip;
use DirHandle;
use File::Basename;
use LWP::Simple;
use Web::Scraper;
use URI;
use YAML;

my $config   = "$ENV{HOME}/.subtitle-downloader.yaml";
@ARGV or die "Usage: subtitle-downloader filename [file ...]\n";

my $conf = read_config($config);
my $counter = 0;

for my $filename (@ARGV) {
    warn "Checking $filename ...\n";
    my $file = File->new($filename);

    my @try = ($file->basename . ".srt", $file->basename . ".$conf->{lang}.srt"); 
    if (my($exist) = grep -e $_, @try) {
	warn "$exist already exists. Skipped\n";
	next;
    }

    my $url = $conf->{shows}->{$file->show}
        or die "Don't know the Opensubtitle URL for the show: ", $file->show;

    my @subs = find_subs($url);
    my @subfiles;
    my($want, $want_filename);
    for my $sub (@subs) {
	next unless $sub->{filename} && $sub->{filename} !~ /Download at/;
	my $subfile = File->new($sub->{filename});
	next unless $subfile->season && $subfile->episode;
	
	if ($subfile->basename eq $file->basename) {
	    $want = $sub->{download};
	    $want_filename = $sub->{filename}
	}
	push @subfiles, [ $subfile, $sub ];
    }

    unless ($want) {
	for my $stuff (@subfiles) {
	    my $subfile = $stuff->[0];
	    if ($subfile->season  == $file->season &&
		$subfile->episode == $file->episode) {
		$want = $stuff->[1]->{download};
		$want_filename = $stuff->[1]->{filename};
		last;
	    }
	}
    }

    if ($want) {
	my $local = $file->basename . ".$conf->{lang}.srt";
	warn "Found $want_filename\n";
	download_extract($want, $local);
    } else {
	warn "Can't find subs for ", $file->basename, "\n";
    }
}

sub download_extract {
    my($want, $local) = @_;
    warn "Start downloading $want\n";
    
    my $tmp = "tmp-" . $$ . "-" . $counter++ . ".zip";
    LWP::Simple::mirror($want => $tmp);

    my $zip = Archive::Zip->new;
    $zip->read($tmp) == Archive::Zip::AZ_OK or die "unzipping $tmp failed";
 
    my $done;   
    for my $member ($zip->members) {
	if ($member->fileName =~ /\.srt$/) {
	    $zip->extractMemberWithoutPaths($member, $local);
	    warn "Sub file extracted to $local\n";
	    $done++;
	    last;
	}
    }

    unless ($done) {
	warn "Can't find .srt files in $zip";
    }

    unlink $tmp;
}

sub read_config {
    my $config = shift;
    my $conf = eval { YAML::LoadFile($config) };
    if ($@) {
	my $example = YAML::Dump({ shows => { Heroes => "http://www.opensubtitles.org/en/search/sublanguageid-eng/idmovie-19576" } });
        die "Error reading config file $config: $@\n\nExample:\n$example";
    }

    # alias the show with lowercase
    for my $show (keys %{$conf->{shows}}) {
	$conf->{shows}->{lc $show} = $conf->{shows}->{$show};
    }
    $conf->{lang} ||= "en";

    return $conf;
}

my %Cache;

sub find_subs {
    my $uri = URI->new(shift);
    unless ($Cache{$uri}) {
	my $scraper = scraper {
	    process q{//tr[contains(@class, 'change')]},
	    'subtitles[]' => scraper {
		process "//td[1]/node()[3]", filename => 'TEXT';
		process "//td[5]/a", download => '@href';
	    };
	};
	$Cache{$uri} = $scraper->scrape($uri)->{subtitles} || [];
    }
    return @{$Cache{$uri}};
}

package File;

sub new {
    my($class, $filename) = @_;
    my $self = bless { filename => $filename }, $class;
    $self->analyze_filename;
    return $self;
}

sub filename { shift->{filename} }
sub basename { shift->{basename} }
sub show     { shift->{show}     }
sub season   { shift->{season}   }
sub episode  { shift->{episode}  }

sub analyze_filename {
    my $self = shift;
    
    my $base = $self->filename;
       $base =~ s/\.(?:avi|mkv|mp4|srt|sub)$//;

    $self->{basename} = $base;

    $base =~ s/\.(HR|[HP]DTV|WS|AAC|AC3|DVDRip|PROPER|DVDSCR|720p|1080p|[hx]264(?:-\w+)?|dd51)\.(.*)//i;
    $base =~ s/ /\./g;

    if ($base =~ s/\.s(\d+)[\.x]?e(\d+).*//i) {
	$self->{season}  = $1 + 0;
	$self->{episode} = $2 + 0;
    } elsif ($base =~ s/\.(\d+)x(\d+).*//i) {
	$self->{season}  = $1 + 0;
	$self->{episode} = $2 + 0;
    } elsif ($base =~ s/\.(\d\d?)(\d\d)(?:\-\w+)?$//) {
	$self->{season}  = $1 + 0;
	$self->{episode} = $2 + 0;
    } else {
#	warn "Can't get season and episode from $base";
    }

    $base =~ s/\./ /g;
    $self->{show} = $base;
}
